<?php if(! defined('SYSPATH')) exit('No direct script access allowed');
/*
 +----------------------------------------------------------------------+
 | QuickPHP Framework Version 0.10                                      |
 +----------------------------------------------------------------------+
 | Copyright (c) 2010 QuickPHP.net All rights reserved.                 |
 +----------------------------------------------------------------------+
 | Licensed under the Apache License, Version 2.0 (the 'License');      |
 | you may not use this file except in compliance with the License.     |
 | You may obtain a copy of the License at                              |
 | http://www.apache.org/licenses/LICENSE-2.0                           |
 | Unless required by applicable law or agreed to in writing, software  |
 | distributed under the License is distributed on an 'AS IS' BASIS,    |
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
 | implied. See the License for the specific language governing         |
 | permissions and limitations under the License.                       |
 +----------------------------------------------------------------------+
 | Author: BoPo <ibopo@126.com>                                         |
 +----------------------------------------------------------------------+
*/
/**
 * QuickPHP 数据库中结果集的包装。
 *
 * @category   QuickPHP
 * @package    Database
 * @author     QuickPHP Team
 * @copyright  (c) 2008-2009 QuickPHP Team
 * @license    http://www.QuickPHP.net/license
 */
abstract class QuickPHP_Database_Result implements Countable, Iterator, SeekableIterator, ArrayAccess
{

    // Executed SQL for this result
    protected $_query;

    // Raw result resource
    protected $_result;

    // Total number of rows and current row
    protected $_total_rows = 0;

    // ++
    protected $_current_row = 0;

    // Return rows as an object or associative array
    protected $_as_object;

    /**
     * Sets the total number of rows and stores the result locally.
     *
     * @param   mixed   query result
     * @param   string  SQL query
     * @return  void
     */
    public function __construct($result, $sql, $as_object)
    {
        // Store the result locally
        $this->_result = $result;

        // Store the SQL locally
        $this->_query = $sql;

        if(is_object($as_object))
        {
            // Get the object class name
            $as_object = get_class($as_object);
        }

        // Results as objects or associative arrays
        $this->_as_object = $as_object;
    }

    /**
     * Result destruction cleans up all open result sets.
     *
     * @return  void
     */
    abstract public function __destruct();

    /**
     * Get a cached database result from the current result iterator.
     *
     * $cachable = serialize($result->cached());
     *
     * @return  Database_Result_Cached
     * @since   3.0.5
     */
    public function cached()
    {
        return new Database_Result_Cached($this->as_array(), $this->_query, $this->_as_object);
    }

    /**
     * Return all of the rows in the result as an array.
     *
     * // Indexed array of all rows
     * $rows = $result->as_array();
     *
     * // Associative array of rows by "id"
     * $rows = $result->as_array('id');
     *
     * // Associative array of rows, "id" => "name"
     * $rows = $result->as_array('id', 'name');
     *
     * @param   string  column for associative keys
     * @param   string  column for values
     * @return  array
     */
    public function as_array($key = NULL, $value = NULL)
    {
        $results = array();

        if($key === NULL and $value === NULL)
        {
            // Indexed rows
            foreach ($this as $row)
            {
                $results[] = $row;
            }
        }
        elseif($key === NULL)
        {
            // Indexed columns
            if($this->_as_object)
            {
                foreach ($this as $row)
                {
                    $results[] = $row->$value;
                }
            }
            else
            {
                foreach ($this as $row)
                {
                    $results[] = $row[$value];
                }
            }
        }
        elseif($value === NULL)
        {
            // Associative rows
            if($this->_as_object)
            {
                foreach ($this as $row)
                {
                    $results[$row->$key] = $row;
                }
            }
            else
            {
                foreach ($this as $row)
                {
                    $results[$row[$key]] = $row;
                }
            }
        }
        else
        {
            // Associative columns
            if($this->_as_object)
            {
                foreach ($this as $row)
                {
                    $results[$row->$key] = $row->$value;
                }
            }
            else
            {
                foreach ($this as $row)
                {
                    $results[$row[$key]] = $row[$value];
                }
            }
        }

        $this->rewind();
        return $results;
    }

    /**
     * Return the named column from the current row.
     *
     * // Get the "id" value
     * $id = $result->get('id');
     *
     * @param   string  column to get
     * @param   mixed   default value if the column does not exist
     * @return  mixed
     */
    public function get($name, $default = NULL)
    {
        $row = $this->current();

        if($this->_as_object)
        {
            if(isset($row->$name))
            {
                return $row->$name;
            }
        }
        else
        {
            if(isset($row[$name]))
            {
                return $row[$name];
            }
        }

        return $default;
    }

    /**
     * Implements [Countable::count], returns the total number of rows.
     *
     * echo count($result);
     *
     * @return  integer
     */
    public function count()
    {
        return $this->_total_rows;
    }

    /**
     * Implements [ArrayAccess::offsetExists], determines if row exists.
     *
     * if (isset($result[10]))
     * {
     * // Row 10 exists
     * }
     *
     * @return  boolean
     */
    public function offsetExists($offset)
    {
        return ($offset >= 0 and $offset < $this->_total_rows);
    }

    /**
     * Implements [ArrayAccess::offsetGet], gets a given row.
     *
     * $row = $result[10];
     *
     * @return  mixed
     */
    public function offsetGet($offset)
    {
        if(! $this->seek($offset)) return NULL;
        return $this->current();
    }

    /**
     * Implements [ArrayAccess::offsetSet], throws an error.
     *
     * [!!] You cannot modify a database result.
     *
     * @return  void
     * @throws  Database_Exception
     */
    final public function offsetSet($offset, $value)
    {
        throw new Database_Exception('Database results are read-only');
    }

    /**
     * Implements [ArrayAccess::offsetUnset], throws an error.
     *
     * [!!] You cannot modify a database result.
     *
     * @return  void
     * @throws  Database_Exception
     */
    final public function offsetUnset($offset)
    {
        throw new Database_Exception('Database results are read-only');
    }

    /**
     * Implements [Iterator::key], returns the current row number.
     *
     * echo key($result);
     *
     * @return  integer
     */
    public function key()
    {
        return $this->_current_row;
    }

    /**
     * Implements [Iterator::next], moves to the next row.
     *
     * next($result);
     *
     * @return  $this
     */
    public function next()
    {
        ++$this->_current_row;
        return $this;
    }

    /**
     * Implements [Iterator::prev], moves to the previous row.
     *
     * prev($result);
     *
     * @return  $this
     */
    public function prev()
    {
        --$this->_current_row;
        return $this;
    }

    /**
     * Implements [Iterator::rewind], sets the current row to zero.
     *
     * rewind($result);
     *
     * @return  $this
     */
    public function rewind()
    {
        $this->_current_row = 0;
        return $this;
    }

    /**
     * Implements [Iterator::valid], checks if the current row exists.
     *
     * [!!] This method is only used internally.
     *
     * @return  boolean
     */
    public function valid()
    {
        return $this->offsetExists($this->_current_row);
    }
}